package com.igoal.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import com.igoal.exception.BeanAttributeNotFoundException;
import com.igoal.exception.SynchronizeObjectException;

public class BaseSynchronizeAttributeWithOther<T> implements
		ISynchronizeObjectAttribute<T> {

	private static final Log s_logger = LogFactory
			.getLog(BaseSynchronizeAttributeWithOther.class);

	@SuppressWarnings("unchecked")
	public T convertTo(Class<T> destCls) throws SynchronizeObjectException {
		Object destObj = null;
		try {
			destObj = ObjectHelper.instantiateObject(destCls);
		} catch (Exception e) {
			String msg = "Couldn't instatiate dest object " + destCls.getName();
			s_logger.error(msg, e);
			throw new SynchronizeObjectException(msg, e);
		}
		if (destObj != null) {
			copyAttributesTo((T)destObj);
		}
		return (T) destObj;
	}

	public void copyAttributesTo(T destObj) throws SynchronizeObjectException {
		synchronizeObjectAttributes(this, destObj, this.getClass(),
				SynchronizeDirection.TO, false);
	}
	
	/**
	 * Get all properties from entity and call the set-method in model. If
	 * there's no set-method for this property, just ignore it :)
	 */
	public void initFrom(T srcObj) {
		synchronizeObjectAttributes(srcObj, this, this.getClass(),
				SynchronizeDirection.FROM, true);
	}

	/**
	 * Synchronize attribute values from src to dest.
	 * 
	 * @param src
	 *            source object
	 * @param dest
	 *            dest object
	 * @param annotatedCls
	 *            class with annotation on (for specific cases)
	 * @param direction
	 *            a direction annotated on annotatedCls to use for specific
	 *            cases
	 * @param isFromSrcDirection
	 *            to determine if the value is come from src object or need to
	 *            convert to dest obj
	 */
	private void synchronizeObjectAttributes(Object src, Object dest,
			Class<?> annotatedCls, SynchronizeDirection direction,
			boolean isFromSrcDirection) throws SynchronizeObjectException {
		// Find methods with @SynchronizeWithEntity on annotatedCls
		List<Method> methods = ObjectHelper.findAllAnnotationDeclaredMethods(
				annotatedCls, SynchronizeAttributeWithOther.class);
		Map<String, Method> attributeMethodMap = new HashMap<String, Method>();
		for (Method m : methods) {
			String entityAttributeName = ((SynchronizeAttributeWithOther) m
					.getAnnotation(SynchronizeAttributeWithOther.class))
					.value();
			SynchronizeDirection currentDirection = ((SynchronizeAttributeWithOther) m
					.getAnnotation(SynchronizeAttributeWithOther.class))
					.direction();
			if (direction.equals(currentDirection)) {
				attributeMethodMap.put(entityAttributeName, m);
			}
		}

		try {
			
			ObjectHelper.copyBeanAttributes(src, dest, true);
			
		} catch (BeanAttributeNotFoundException bnfe) {
			// Just ignore any not-existed attributes
			s_logger.debug("Ignore any not-existed attribute");
		}
		
		BeanWrapper srcBW = new BeanWrapperImpl(src);
		BeanWrapper destBW = new BeanWrapperImpl(dest);
		
		// Execute the method with @SynchronizeWithOther
		for (String srcProperty : attributeMethodMap.keySet()) {
			Method m = attributeMethodMap.get(srcProperty);
			
			if (m != null) {
				try {
					if (isFromSrcDirection) {
						Object srcPropertyValue = srcBW.getPropertyValue(
								srcProperty);
						m.invoke(this, new Object[] { srcPropertyValue });
					} else {
						Object returnValue = null;
						returnValue = m.invoke(this, new Object[] {});
						destBW.setPropertyValue(srcProperty, returnValue);
					}
				} catch (IllegalArgumentException iae) {
					String msg = new StringBuffer(
							"Could not execute @SynchronizeWithOther function")
							.append(m.getName()).append(" from ").append(
									src.getClass().getName()).append("to ")
							.append(dest.getClass().getName()).toString();
					s_logger.error(msg, iae);
					throw new SynchronizeObjectException(msg, iae);
				} catch (IllegalAccessException iae) {
					String msg = new StringBuffer(
							"Could not execute @SynchronizeWithOther function")
							.append(m.getName()).append(" from ").append(
									src.getClass().getName()).append("to ")
							.append(dest.getClass().getName()).toString();
					s_logger.error(msg, iae);
					throw new SynchronizeObjectException(msg, iae);
				} catch (InvocationTargetException ite) {
					String msg = new StringBuffer(
							"Exception when execute @SynchronizeWithOther function")
							.append(m.getName()).append(" from ").append(
									src.getClass().getName()).append("to ")
							.append(dest.getClass().getName()).toString();
					s_logger.error(msg, ite.getTargetException());
					throw new SynchronizeObjectException(msg, ite);
				}
			}
		}
	}
}
